package com.baturu.mybatisminus.util;

import com.baturu.mybatisminus.base.Pageable;
import com.baturu.mybatisminus.base.Sort;
import com.baturu.mybatisminus.model.MethodInfo;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * MethodParseUtils
 *
 * @author huangbin
 **/
public class MethodParseUtils {

    public static MethodInfo parseMethod(Class<?> mapperClass, Method method) {
        MethodInfo methodInfo = new MethodInfo();
        methodInfo.setMethodName(method.getName());
        methodInfo.setParameterType(MethodUtils.getParameterType(method));
        methodInfo.setResultType(MethodUtils.getReturnType(mapperClass, method));

        List<Class<?>> paramTypes = Arrays.stream(method.getParameterTypes())
                .filter(c -> !isSpecialParameter(c))
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(paramTypes)) {
            return methodInfo;
        }

        boolean pageParamExist = paramTypes.stream().anyMatch(Pageable.class::isAssignableFrom);
        boolean sortParamExist = paramTypes.stream().anyMatch(Sort.class::isAssignableFrom);
        methodInfo.setPageParamExist(pageParamExist);
        methodInfo.setSortParamExist(sortParamExist);

        if (paramTypes.size() == 1) {
            if (isPage(paramTypes.get(0))) {
                methodInfo.setPageParamName(getParamNameOfOnlyOneParam(method));
            } else if (isSort(paramTypes.get(0))) {
                methodInfo.setSortParamName(getParamNameOfOnlyOneParam(method));
            } else {
                methodInfo.setOnlyOneParamName(getParamNameOfOnlyOneParam(method));
            }
        }

        if (pageParamExist && methodInfo.getPageParamName() == null) {
            methodInfo.setPageParamName(getPageParamName(paramTypes));
        }
        if (sortParamExist && methodInfo.getSortParamName() == null) {
            methodInfo.setSortParamName(getSortParamName(paramTypes));
        }
        return methodInfo;

    }


    private static boolean isPage(Class<?> clazz) {
        return Pageable.class.isAssignableFrom(clazz);
    }

    private static boolean isSort(Class<?> clazz) {
        return Sort.class.isAssignableFrom(clazz);
    }

    private static boolean isSpecialParameter(Class<?> clazz) {
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }


    /**
     * 有且只有一个参数(包括Pageable,Sort)时获取该参数可能的名称，如果用户使用了@Param注解那么就是注解指定的值，
     * 如果没使用注解而参数类型是List,Collection,array则mybatis会将其命名为相应值，
     * 如果都不是上边的情况mybatis直接将参数返回，具体可参考mybatis源码
     */
    private static String getParamNameOfOnlyOneParam(Method method) {
        final Class<?> paramType = method.getParameterTypes()[0];
        final Annotation[] paramAnnotations = method.getParameterAnnotations()[0];
        for (Annotation annotation : paramAnnotations) {
            if (annotation instanceof Param) {
                return ((Param) annotation).value();
            }
        }
        if (List.class.isAssignableFrom(paramType)) {
            return "list";
        } else if (Collection.class.isAssignableFrom(paramType)) {
            return "collection";
        } else if (paramType.isArray()) {
            return "array";
        }
        return "";
    }


    private static String getPageParamName(List<Class<?>> paramTypes) {
        for (int i = 0; i < paramTypes.size(); i++) {
            if (Pageable.class.isAssignableFrom(paramTypes.get(i))) {
                return "param" + (i + 1);
            }
        }
        return "";
    }

    private static String getSortParamName(List<Class<?>> paramTypes) {
        for (int i = 0; i < paramTypes.size(); i++) {
            if (Sort.class.isAssignableFrom(paramTypes.get(i))) {
                return "param" + (i + 1);
            }
        }
        return "";
    }

}
